import base64
from datetime import datetime
from functools import reduce

from flask_login import UserMixin, AnonymousUserMixin, LoginManager
from flask import current_app
from datetime import datetime

from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import AbstractConcreteBase, declared_attr
from sqlalchemy.orm.attributes import InstrumentedAttribute
from werkzeug.security import generate_password_hash, check_password_hash
from sqlalchemy import Table, DateTime, func, UniqueConstraint
from . import db, login_manager
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from sqlalchemy import Column, ForeignKey
from sqlalchemy.types import String, Integer, CHAR, BIGINT

class BaseModel(object):

    # @declared_attr
    # def __tablename__(cls):
    #     return cls.__name__.lower()

    #__table_args__ = {'mysql_engine': 'InnoDB'}


    id = db.Column(db.Integer, primary_key=True,autoincrement=True,nullable=False)
    created = db.Column(db.DateTime, default=func.now())
    updated = db.Column(db.DateTime,)
    deleted = db.Column(db.String(1), default='0')



    def dict(self,cloumns):
        cls =self.__class__

        result = {}
        if not  cloumns:
            return result
        for c in cloumns:
            t = cls.__dict__.get(c,None)
            if t and issubclass(type(t),(InstrumentedAttribute,)):
                result[c] = getattr(self,c)

        return result
'''
####################################  sys 模块的module  ####################################
'''



class AbstractAction(BaseModel,AbstractConcreteBase):

    bit_weight = db.Column(db.BigInteger(), autoincrement=True,doc='位权重')
    name = db.Column(db.String(64), unique=True)
    code = db.Column(db.String(64), unique=True)
    action = db.Column(db.String(64), unique=True)
    method = db.Column(db.String(64))
    uri = db.Column(db.String(64))

    @declared_attr
    def __mapper_args__(cls):
        # configurate subclasses about concrete table inheritance
        return {'polymorphic_identity': cls.__name__,
                'concrete': True} if cls.__name__ != "AbstractAction" else {}


class DefaultAction(AbstractAction,db.Model):
    __tablename__ = 'sys_default_action'



class Resource(BaseModel,db.Model):
    __tablename__ = 'sys_resource'
    order = db.Column(db.Integer)
    parent_id = db.Column(db.Integer, db.ForeignKey('sys_resource.id'))
    code = db.Column(db.String(64),)
    name = db.Column(db.String(64), unique=True)
    path = db.Column(db.String(64),nullable=True)
    res_type = db.Column(db.String(64))
    parent_resource = db.relationship("Resource", foreign_keys=[parent_id],remote_side="Resource.id")




class Action(AbstractAction,db.Model):
    __tablename__ = 'sys_action'

    resource_id = db.Column(db.Integer, db.ForeignKey('sys_resource.id'))
    resource = db.relationship("Resource", foreign_keys=[resource_id],backref='actions')


    __table_args__ = (  # number name 检查联合唯一
        UniqueConstraint("resource_id", "bit_weight"),
    )


class Authority(BaseModel,db.Model):
    __tablename__ = 'sys_authority'
    group_id = db.Column(db.Integer,db.ForeignKey('sys_group.id'))
    action_id = db.Column(db.Integer,db.ForeignKey('sys_action.id'))

    action = db.relationship('Action')
    group = db.relationship('Group')


    #def getResourcesAndAction(self,group_id):

    def can_access(self,user_id,action):

        pass


user_group_table = Table('sys_user_group_rel', db.metadata,
    Column('id', Integer, primary_key=True,autoincrement=True,nullable=False),
    Column('user_id', Integer, ForeignKey('sys_user.id')),
    Column('group_id', Integer, ForeignKey('sys_group.id'))
)


class Group(BaseModel,db.Model):
    __tablename__ = 'sys_group'
    parent_id = db.Column(db.Integer, db.ForeignKey('sys_group.id'))
    name = db.Column(db.String(64), unique=True)
    users = db.relationship("User", secondary=user_group_table )

class User(UserMixin,BaseModel,db.Model):
    __tablename__ = 'sys_user'
    username = db.Column(db.String(64), unique=True)
    name = db.Column(db.String(64), unique=True)
    email = db.Column(db.String(64), unique=True, index=True)
    password_hash = db.Column(db.String(128))

    groups = db.relationship("Group", secondary=user_group_table)
    #todo group inherit

    group_ids = association_proxy('groups', 'id')

    def authed_actions(self):
        grouped_auths = [
            Authority.query.join(Group, Group.id == Authority.group_id).filter(Authority.group_id == group_id).all() for
            group_id in self.group_ids]
        return [y.action for x in grouped_auths for y in x]

    def resources(self):
        authed_actions = self.authed_actions()

        authed_resources = [act.resource for act in authed_actions]

        rs_set = set(authed_resources)
        for r in authed_resources:
            tmp = r
            while tmp.parent_resource:
                rs_set.add(tmp.parent_resource)
                tmp = tmp.parent_resource
        return rs_set


    def can(self,local_uri,method):

        request_action = Action.query.filter(Action.method == method, Action.uri == local_uri).one_or_none()
        if not request_action:
            #  resources that are not controlled by permissions can be not accessed,By default
           return False

        request_action_perm =  1<< request_action.bit_weight

        grouped_auths = [Authority.query.join(Group,Group.id ==Authority.group_id ).filter(Authority.group_id==group_id).all() for group_id in self.group_ids]
        if not grouped_auths:
            return False

        all_auths =[y for x in grouped_auths for y in x]
        authed_actions =[ auth.action for auth in all_auths]
        perms = reduce(lambda a,b:a.bit_weight|(1<<b.bit_weight),authed_actions)

        return True if perms & request_action_perm == request_action_perm else False



    def can_access(self,resource_id):
        #for group in self.groups:
        pass


    @property
    def password(self):
        raise AttributeError('password is not a readable attribute')

    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        flag = check_password_hash(self.password_hash, password)
        return flag

    @staticmethod
    def verify_auth_token(token):
        s = Serializer(current_app.config['SECRET_KEY'])
        try:
            data = s.loads(token)
        except:
            return None
        return User.query.get(data['id'])

    def __repr__(self):
        return '<User %r>' % self.username


@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))
    login_manager.init_app(app)

'''
####################################    模块的module  ####################################
'''


